package com.gmcloud.auth.config;

import com.gmcloud.auth.support.core.CustomOAuth2AccessTokenGenerator;
import com.gmcloud.auth.support.core.CustomOAuth2TokenCustomizer;
import com.gmcloud.auth.support.core.FormIdentityLoginConfigurer;
import com.gmcloud.auth.support.core.GmDaoAuthenticationProvider;
import com.gmcloud.auth.support.handler.GmAuthenticationFailureEventHandler;
import com.gmcloud.auth.support.handler.GmAuthenticationSuccessEventHandler;
import com.gmcloud.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter;
import com.gmcloud.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider;
import com.gmcloud.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter;
import com.gmcloud.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;
import com.gmcloud.common.core.constant.SecurityConstants;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.Arrays;

/**
 * @author zl.sir
 * @version 1.0
 * @since 2022/8/17 23:33
 * 认证服务器配置
 */

@Configuration
public class AuthorizationServerConfiguration {

    private final OAuth2AuthorizationService authorizationService;

    public AuthorizationServerConfiguration(OAuth2AuthorizationService authorizationService) {
        this.authorizationService = authorizationService;
    }

    /**
     * 第一
     * OAuth2ClientAuthenticationFilter
     * OAuth2TokenEndpointFilter
     * DelegatingAuthenticationConverter
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();
        // 个性化认证授权端点
        http.apply(authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->
                                // 注入自定义的授权认证Converter
                                tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter())
                                        // 登录成功处理器,输出token
                                        .accessTokenResponseHandler(new GmAuthenticationSuccessEventHandler())
                                        // 登录失败处理器
                                        .errorResponseHandler(new GmAuthenticationFailureEventHandler())
                        // 个性化客户端认证
                ).clientAuthentication(oAuth2ClientAuthenticationConfigurer ->
                        // 处理客户端认证异常
                        oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new GmAuthenticationFailureEventHandler()))
                // 授权码端点个性化confirm页面
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                        .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));

        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
        DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
                .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
                // redis存储token的实现
                .apply(authorizationServerConfigurer.authorizationService(authorizationService)
                        .providerSettings(ProviderSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()))
                // 授权码登录的登录页个性化
                .and().apply(new FormIdentityLoginConfigurer()).and().build();

        // 注入自定义授权模式实现
        addCustomOAuth2GrantAuthenticationProvider(http);
        return securityFilterChain;
    }


    /**
     * request -> xToken 注入请求转换器，根据请求中的参数和授权类型组装成对应的授权认证对象
     *
     * @return DelegatingAuthenticationConverter
     */
    private AuthenticationConverter accessTokenRequestConverter() {
        return new DelegatingAuthenticationConverter(Arrays.asList(
                // 密码认证转换
                new OAuth2ResourceOwnerPasswordAuthenticationConverter(),
                // 短信登录转换
                new OAuth2ResourceOwnerSmsAuthenticationConverter(),
                new OAuth2RefreshTokenAuthenticationConverter(),
                // 客户端授权转换
                new OAuth2ClientCredentialsAuthenticationConverter(),
                // 根据授权码获取token
                new OAuth2AuthorizationCodeAuthenticationConverter(),

                // 授权码
                new OAuth2AuthorizationCodeRequestAuthenticationConverter()));
    }

    /**
     * 注入授权模式实现提供方
     * <p>
     * 1. 密码模式 </br>
     * 2. 短信登录 </br>
     */
    private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);

        OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
                authenticationManager, authorizationService, oAuth2TokenGenerator());

        OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
                authenticationManager, authorizationService, oAuth2TokenGenerator());

        // 处理 UsernamePasswordAuthenticationToken
        // 用户账号密码验证
        http.authenticationProvider(new GmDaoAuthenticationProvider());
        // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
        http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
        // 处理 OAuth2ResourceOwnerSmsAuthenticationToken
        http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
    }


    /**
     * 令牌生成规则实现 </br>
     * client:username:uuid
     *
     * @return OAuth2TokenGenerator
     */
    @Bean
    public  OAuth2TokenGenerator<? extends OAuth2Token> oAuth2TokenGenerator() {
        CustomOAuth2AccessTokenGenerator accessTokenGenerator = new CustomOAuth2AccessTokenGenerator();
        // 注入Token 增加关联用户信息
        accessTokenGenerator.setAccessTokenCustomizer(new CustomOAuth2TokenCustomizer());
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
    }

}
